﻿#region License
/*
Copyright (c) 2009, G.W. van der Vegt
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided 
that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the 
  following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
  the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of G.W. van der Vegt nor the names of its contributors may be 
  used to endorse or promote products derived from this software without specific prior written 
  permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
*/
#endregion License

#region changelog

//----------   ---   -------------------------------------------------------------------------------
//Purpose:           Testbed for Data Collection
//                   Self-Saving & Loading thru reflection.
//
//                   Encapsulates two User Defined Data Storage Class: 
//                    one global for setup and 
//                    one for individual record or measurements.
//Usage:             private Serie<Setup, Measurement> serie = Serie<Setup, Measurement>.Instance;
//                   where:
//                    Setup is a user defined class containing setup data and 
//                    Measurement is a class representing an (measurement/record)item in the Collection.
//
//By:                G.W. van der Vegt (wvd_vegt@knoware.nl)
//Url:               http://swiss.codeplex.com
//Depends:           IniFile,
//                   Storage.
//Based on:          Singleton Pattern at http://dotnetperls.com/Content/Singleton-Static.aspx
//                   And much More...
//License:           New BSD License
//----------   ---   -------------------------------------------------------------------------------
//dd-mm-yyyy - who - description
//----------   ---   -------------------------------------------------------------------------------
//09-12-2008 - veg - Started on Saving from IniCollection.
//                 - Converted to Collection Decendant.
//10-12-2008 - veg - Added Two External Classes for Payload.
//                 - Kept bookkeeping like Count/Dirty in base class.
//                 - Used generics to accomplish above.
//                 - Added Reading of IniCollection (Support for Enum, Int32, Double, String and Boolean).
//                 - Extracted WriteProperty (Move to IniCollection?).
//                 - Extracted ReadProperty  (Move to IniCollection?).
//11-12-2008 - veg - Added reading Measurements.
//12-12-2008 - veg - Added Support for Loading Defaults from Setup.Settings and Measurement.Settings.
//                 - Moved Measurement and Setup classes into Applications Namespace.
//                 - Turned Data into a Library.
//                 - Added Dirty Support for Payload Properties.
//                 - Added Text Property.
//                 - Debugged Storage Support.
//                 - Support for Both Storage and Single ini file output.
//                 - Replaced all int by Int32.
//                 - Added Documentation stubs.
//16-12-2008 - veg - Changed namespace into Swiss.  
//21-12-2008 - veg - Properties must now have a SectionAttribute to be read/written from/to storage.
//                 - This is done to be able to have properties for other purposes like displaying.
//12-12-2009   veg - Corrected FileName behaviour (rely for this on both IniFile and Storage)! 
//                   If no path is passed, the file is created in a directory under %APPDATA%.
//                   The directory is either named after the Application Title or, 
//                   if absent, the Application Executable Name.
//----------   ---   -------------------------------------------------------------------------------
//dd-mm-yyyy         todo's
//----------   ---   -------------------------------------------------------------------------------
//                 - Beware that Boolean are stored as True/False where 1/0 would be better!
//
//                 - Look into IniFile being an IDisposable (missing a normal dispose())!
//                 - Check if we can use Storage as IDisposable parameter!
//
//                 - Test ListSeparator and Enum in IniFile!
//                 - Check if setting the tread's culture is ok.
//
//                 - Use Attributes to specify the section where stuff is stored.
//                   Make it that if the SectionAttribute is Empty, only the [Attribute] is used
//                   else use [SectionAttribute.Attribute].
// 
//                 - Load/Save Setup, Load/SaveMeasurements.
//                    Storage2Setup() / Setup2Storage()
//                    Storage2Data()  / Data2Storage(no: integer)
//                    StorageSample2Data(name: string) (this is a helper for Storage2Data())
//          
//                 - Items in separate File.
//                 - Bind to Storage.
//----------   ---   -------------------------------------------------------------------------------
//dd-mm-yyyy         todo's
//----------   ---   -------------------------------------------------------------------------------
//                 - Sealed Class cannot be used as Base Class.
//                 - Beware that Boolean are stored as True/False where 1/0 would be better.
//                 - By default save the Decimal Separator/List Separator/Date Format for better parsing.
//----------   ---   -------------------------------------------------------------------------------

#endregion changelog

namespace Swiss
{
    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.Diagnostics;
    using System.Globalization;
    using System.Reflection;
    using System.Threading;
    using Swiss;

    #region SectionAttribute

    /// <summary>
    /// 
    /// </summary>
    [AttributeUsage(AttributeTargets.Property,
        AllowMultiple = false)]
    public class SectionAttribute : Attribute
    {
        /// <summary>
        /// 
        /// </summary>
        public string IniSection
        {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="IniSection"></param>
        public SectionAttribute(string IniSection)
        {
            this.IniSection = IniSection;
        }
    }

    #endregion SectionAttribute

    /// <summary>
    /// Event for IDirty signalling a property has changed of value.
    /// </summary>
    public delegate void OnChange();

    #region IDirty Interface

    /// <summary>
    /// Interface used to implement support for a Dirty Flag indicating the 
    /// Collection has been changed and needs to be saved.
    /// </summary>
    public interface IDirty
    {
        /// <summary>
        /// An event handler that needs to be called when a property is changed.
        /// </summary>
        event OnChange DoChange;

        /// <summary>
        /// This function is used to test if a Property Value is different from 
        /// a previous value and if so call the DoChanged event.
        /// 
        /// This function needs to be implemented as follow:
        /// 
        /// if (!prop.Equals(value))
        ///  {
        ///    if (DoChange != null)
        ///     {
        ///         DoChange();
        ///     }
        /// }
        ///
        /// return value;
        /// 
        /// It should be used inside the property set code as follow:
        /// 
        /// prop = (proptype)(CompareAndSetDirty(prop, value));
        /// 
        /// </summary>
        /// <param name="prop">The property.</param>
        /// <param name="value">The properties possibly new value.</param>
        /// <returns>The properties new value.</returns>
        object CompareAndSetDirty(object prop, object value);
    }

    #endregion IDirty Interface

    /// <summary>
    /// Event to perform simple value translation during reading of Properties.
    /// </summary>
    /// <param name="aSection"></param>
    /// <param name="aName"></param>
    /// <param name="aValue"></param>
    /// <returns></returns>
    public delegate string OnTranslate(string aSection, string aName, string aValue);

    /// <summary>
    /// Sealed Classes cannot be used as Base Class (see Instance), 
    /// but with two payload classes we do not need to subclass it!
    /// 
    /// </summary>
    /// <remarks>Both classes need to implement the IDirty interface</remarks>
    /// <typeparam name="T">A class containing the application dependend payload of the whole collection.</typeparam>
    /// <typeparam name="S">A class containing the application dependend payload of a collection item.</typeparam>
    public sealed class Serie<T, S> : CollectionBase, IDisposable
        where T : class
        where S : class
    {
        #region Item

        /// <summary>
        /// This class implements a Collection Item containing a application dependend class U as payload.
        /// </summary>
        /// <typeparam name="U">The application dependend payload class.</typeparam>
        public class SerieItem<U>
            where U : class
        {
            /// <summary>
            /// The Index of an Item. The index is copied from the parent Collection class and is unique throught the Collection.
            /// </summary>
            public Int32 Index
            {
                get { return fIndex; }
            }
            private Int32 fIndex = 0;

            /// <summary>
            /// The application dependend payload class.
            /// </summary>
            public U Measurement
            {
                get { return fMeasurement; }
                set
                {
                    fMeasurement = value;
                }
            }
            private U fMeasurement;

            /// <summary>
            /// A reference to the original collection Serie&lt;T,S&gt; the item belongs to.
            /// </summary>
            private Serie<T, S> Collection
            {
                get;
                set;
            }

            /// <summary>
            /// A private funtion to set the parent collections dirty flag.
            /// </summary>
            private void MakeDirty()
            {
                Collection.Dirty = true;
            }

            /// <summary>
            /// The collection item constructor. It creates a reference to the parent collection, 
            /// sets the index of the item created, creates the application dependend payload class
            /// and finally hooks into the DoChange event of the IDirty interface.
            /// </summary>
            /// <param name="aCollection">The parent collection of this collection item.</param>
            public SerieItem(Serie<T, S> aCollection)
            {
                Collection = aCollection;

                fIndex = Collection.NextIndex;

                //Circumvents thee new Constraint Error when using 'data = new U();'.
                ConstructorInfo ci = typeof(S).GetConstructor(Type.EmptyTypes);
                Measurement = ci.Invoke(null) as U;

                //Attach Event Handler for signalling Dirty making changes.
                ((IDirty)Measurement).DoChange += new OnChange(MakeDirty);

                Debug.WriteLine(String.Format("CREATING {0} ITEM OBJECT", GetType().Name));
                Debug.WriteLine(String.Format("CREATING {0} ITEM PAYLOAD", Measurement.GetType().Name));
            }
        }

        #endregion Item

        /// <summary>
        /// Event to perform simple value translation during reading of Properties.
        /// </summary>
        public event OnTranslate DoTranslate;

        #region Properties

        /// <summary>
        /// The application dependend payload of the collection.
        /// </summary>
        public T Setup
        {
            get;
            set;
        }

        /// <summary>
        /// Dirty flag indicating that a property has changed.
        /// <remarks>When a property is rewritten with the same value Dirty does not change.</remarks>
        /// </summary>
        public bool Dirty
        {
            get;
            set;
        }

        /// <summary>
        /// This indexer contains the collections items.
        /// </summary>
        /// <param name="index">The index of the item to access.</param>
        /// <returns>The item requested.</returns>
        public SerieItem<S> this[Int32 index]
        {
            get
            {
                return ((SerieItem<S>)List[index]);
            }
            set
            {
                Dirty = true;

                List[index] = value;
            }
        }

        #endregion Properties

        #region Collection

        /// <summary cref="Add">
        /// Creates and adds a new item and returns it instead of its Index as Add does.
        /// </summary>
        /// <returns>The item added.</returns>
        public SerieItem<S> AddNew()
        {
            return (SerieItem<S>)List[Add(new SerieItem<S>(this))];
        }

        /// <summary>
        /// Adds an item and returns its Index.
        /// </summary>
        /// <param name="value">The item to Add.</param>
        /// <returns>The index of the item added</returns>
        public Int32 Add(SerieItem<S> value)
        {
            Dirty = true;

            Int32 Result = List.Add(value);

            return Result;
        }

        /// <summary>
        /// Gets the index of an item.
        /// </summary>
        /// <param name="value">The item to get the Index of.</param>
        /// <returns>The Index of the item.</returns>
        public Int32 IndexOf(SerieItem<S> value)
        {
            return (List.IndexOf(value));
        }

        /// <summary>
        /// Inserts an item and returns its Index.
        /// </summary>
        /// <param name="index">The index where it insert.</param>
        /// <param name="value">The Item to insert.</param>
        public void Insert(Int32 index, SerieItem<S> value)
        {
            Dirty = true;

            List.Insert(index, value);
        }

        /// <summary>
        /// Removes an Item.
        /// </summary>
        /// <param name="value">The Item to remove.</param>
        public void Remove(SerieItem<S> value)
        {
            Dirty = true;

            List.Remove(value);
        }

        /// <summary>
        /// Test if an Item is present.
        /// </summary>
        /// <param name="value">The Item to find.</param>
        /// <returns>True if found else False.</returns>
        public bool Contains(SerieItem<S> value)
        {
            return (List.Contains(value));
        }

        /// <summary cref="CollectionBase">
        /// -
        /// </summary>
        /// <param name="index">-</param>
        /// <param name="value">-</param>
        protected override void OnInsert(Int32 index, Object value)
        {
            // Insert additional code to be run only when inserting values.
            Debug.WriteLine(String.Format("OnInsert({0},{1}", index.ToString(), value.ToString()));
        }

        /// <summary cref="CollectionBase">
        /// -
        /// </summary>
        /// <param name="index">-</param>
        /// <param name="value">-</param>
        protected override void OnRemove(Int32 index, Object value)
        {
            // Insert additional code to be run only when removing values.
            Debug.WriteLine(String.Format("OnRemove({0},{1}", index.ToString(), value.ToString()));
        }

        /// <summary cref="CollectionBase">
        /// -
        /// </summary>
        /// <param name="index">-</param>
        /// <param name="oldValue">-</param>
        /// <param name="newValue">-</param>
        protected override void OnSet(Int32 index, Object oldValue, Object newValue)
        {
            // Insert additional code to be run only when setting values.
            Debug.WriteLine(String.Format("Onset({0},{1},{2}", index.ToString(), oldValue.ToString(), newValue.ToString()));
        }

        /// <summary  cref="CollectionBase">
        /// -
        /// </summary>
        /// <param name="value">-</param>
        protected override void OnValidate(Object value)
        {
            if (value.GetType() != typeof(SerieItem<S>))
                throw new ArgumentException("value must be of type DataItem.", "value");
        }

        #endregion Collection

        #region Singleton

        /// <summary>
        /// Visible when reflecting, call Private Constructor Once. 
        /// 
        /// Thread Safe!
        /// </summary>
        static readonly Serie<T, S> instance =
            typeof(Serie<T, S>).InvokeMember(typeof(Serie<T, S>).Name,
            BindingFlags.CreateInstance |
            BindingFlags.Instance |
            BindingFlags.NonPublic,
            null, null, null) as Serie<T, S>;

        /// <summary>
        /// 
        /// </summary>
        public static Serie<T, S> Instance
        {
            get { return instance; }
        }

        /// <summary>
        /// Counter starts at 1...
        /// </summary>
        internal Int32 NextIndex
        {
            get
            {
                return ++fNextIndex;
            }
        }
        internal Int32 fNextIndex = 0;

        /// <summary>
        /// Private Constructor!
        /// </summary>
        private Serie()
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.CurrentUICulture;

            //Circumvents thee new Constraint Error when using 'data = new T();'.
            ConstructorInfo ci = typeof(T).GetConstructor(Type.EmptyTypes);
            Setup = ci.Invoke(null) as T;

            Debug.WriteLine(String.Format("CREATING {0} COLLECTION OBJECT", GetType().Name));
            Debug.WriteLine(String.Format("CREATING {0} COLLECTION PAYLOAD", Setup.GetType().Name));
        }

        #endregion Singleton

        /// <summary>
        /// Returns the Data Object as if rendered to a single Ini File.
        /// </summary>
        public String Text
        {
            get
            {
                IniFile ic = new IniFile();

                RenderToIni(ref ic);

                return ic.Text;
            }
        }

        #region Saving

        /// <summary>
        /// 
        /// </summary>
        /// <param name="aFileName"></param>
        /// <returns></returns>
        public bool SaveToStorage(string aFileName)
        {
            using (Storage cd = new Storage(aFileName))
            {
                if (cd.Open())
                {
                    Setup2Storage(cd);

                    //Writeout Data Object's Items...
                    foreach (SerieItem<S> di in this)
                    {
                        Measurement2Storage(cd, di);
                    }
                    cd.Commit();
                }
            } //using

            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="index"></param>
        public void Measurement2Storage(String fileName, Int32 index)
        {
            using (Storage cd = new Storage(fileName))
            {
                if (cd.Open())
                {
                    Measurement2Storage(cd, this[index]);
                }
            } //using
        }

        /// <summary cref="Storage">
        /// Saves the object to a Ole32 Storage.
        /// </summary>
        /// <param name="cd"></param>
        /// <param name="di"></param>
        private void Measurement2Storage(Storage cd, SerieItem<S> di)
        {
            //Note: ref Paramaters and Using cannot be combined.

            IniFile ic = new IniFile();

            RenderMeasurementToIni(
                    ref ic,
                    di.Measurement,
                    null);

            using (StorageStream aStream = new StorageStream(String.Format("data\\sample{0:000}.ini", di.Index), cd.Root))
            {
                aStream.Text = ic.Text;
                aStream.Commit();
            } //using

            //TODO:
            //ic.Dispose();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        public void Setup2Storage(String fileName)
        {
            using (Storage cd = new Storage(fileName))
            {
                if (cd.Open())
                {
                    Setup2Storage(cd);
                }
            } //using
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cd"></param>
        private void Setup2Storage(Storage cd)
        {
            //Note: ref Paramaters and Using cannot be combined.

            IniFile ic = new IniFile();

            RenderSetupToIni(ref ic);

            using (StorageStream aStream = new StorageStream("serie.ini", cd.Root))
            {
                aStream.Text = ic.Text;
                aStream.Commit();
            } //using

            //TODO:
            //ic.Dispose();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public bool SaveToFile(string fileName)
        {
            //Note: ref Paramaters and Using cannot be combined.

            try
            {
                IniFile ic = new IniFile();
                RenderToIni(ref ic);

                if (ic.Dirty)
                {
                    ic.SaveToFile(fileName);
                }

                //TODO:
                //ic.Dispose();

                Dirty = false;
            }
            catch
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ic"></param>
        private void RenderToIni(ref IniFile ic)
        {
            RenderSetupToIni(ref ic);

            //Writeout Data Object's Items...
            foreach (SerieItem<S> di in this)
            {
                RenderMeasurementToIni(
                    ref ic,
                    di.Measurement,
                    String.Format("{0}{1:00}", di.Measurement.GetType().Name, di.Index));
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ic"></param>
        private void RenderSetupToIni(ref IniFile ic)
        {
            ic.WriteString("Locale", "DecimalSeparator",
                CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator);
            ic.WriteString("Locale", "ListSeparator",
                CultureInfo.CurrentUICulture.TextInfo.ListSeparator);

            foreach (PropertyInfo prop in Setup.GetType().GetProperties())
            {
                WriteProperty(ic, null/*Setup.GetType().Name*/, Setup, prop);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ic"></param>
        /// <param name="si"></param>
        /// <param name="section"></param>
        private void RenderMeasurementToIni(ref IniFile ic, S si, string section)
        {
            ic.WriteString("Locale", "DecimalSeparator",
                CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator);
            ic.WriteString("Locale", "ListSeparator",
                CultureInfo.CurrentUICulture.TextInfo.ListSeparator);

            //Writeout Data Object's Items...
            foreach (PropertyInfo prop in si.GetType().GetProperties())
            {
                WriteProperty(
                    ic,
                    section,
                    si,
                    prop);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ic"></param>
        /// <param name="section"></param>
        /// <param name="obj"></param>
        /// <param name="info"></param>
        private void WriteProperty(IniFile ic, String section, Object obj, PropertyInfo info)
        {
            if (HasSectionAttribute(info))
            {
                //Writeout Data Object (Ignore Instance)...
                string nam = info.Name;                             //Get Name from S
                string val = "";
                if (info.GetValue(obj, null) != null)
                {

                    //NOTE: Gives errors as Measuremnt is an Array too...

                    if (info.PropertyType.IsArray)
                    {
                        Array Elements = info.GetValue(obj, null) as Array;

                        Type ElementType = Elements.GetType().GetElementType();

                        Int32 size = 0;
                        String format = "";

                        switch (ElementType.Name)
                        {
                            case "SByte":
                            case "Byte":
                                {
                                    size = 1;
                                    format = String.Format("X{0}", 2 * size);

                                    for (int i = 0; i < Elements.Length; i++)
                                    {
                                        val += System.Convert.ToByte(Elements.GetValue(i)).ToString(format);
                                    }
                                }
                                break;

                            case "Int16":
                            case "UInt16":
                                {
                                    size = 2;
                                    format = String.Format("X{0}", 2 * size);

                                    for (int i = 0; i < Elements.Length; i++)
                                    {
                                        val += System.Convert.ToUInt16(Elements.GetValue(i)).ToString(format);
                                    }
                                }
                                break;

                            case "Int32":
                            case "UInt32":
                                {
                                    size = 4;
                                    format = String.Format("X{0}", 2 * size);

                                    for (int i = 0; i < Elements.Length; i++)
                                    {
                                        val += System.Convert.ToUInt32(Elements.GetValue(i)).ToString(format);
                                    }
                                }
                                break;

                            default:
                                Debug.WriteLine(String.Format("Unsupported Array Type {0}", ElementType.Name));
                                break;
                        }
                    }
                    else
                    {
                        val = info.GetValue(obj, null).ToString();      //Get Value from S if any
                    }
                }
                string typ = info.PropertyType.Name;                //Get Type from S

                ic.WriteString(
                    AlteredSection(section, info, nam),
                    nam,
                    val,
                    typ);
            }
        }

        /// <summary>
        /// Test for the presence of a SectionAttribute.
        /// </summary>
        /// <param name="prop">The property to examine.</param>
        /// <returns>true if a SectionAttribute was found, else false.</returns>
        private static Boolean HasSectionAttribute(PropertyInfo prop)
        {
            object[] atts = prop.GetCustomAttributes(typeof(SectionAttribute), false);
            foreach (Object att in atts)
            {
                if (att is SectionAttribute)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Alter Section Name According to Attribute.
        /// 
        /// Use Attribute either Section Name or as Section Suffix.
        /// </summary>
        /// <param name="section"></param>
        /// <param name="prop"></param>
        /// <param name="nam"></param>
        /// <returns></returns>
        private static String AlteredSection(String section, PropertyInfo prop, string nam)
        {
            object[] atts = prop.GetCustomAttributes(typeof(SectionAttribute), false);
            foreach (Object att in atts)
            {
                if (att is SectionAttribute)
                {
                    if (section == null || section.Trim().Length == 0)
                    {
                        section = ((SectionAttribute)att).IniSection;
                    }
                    else
                    {
                        section += "." + ((SectionAttribute)att).IniSection;
                    }
                    break;
                }
            }

            if (section == "")
            {
                Debug.WriteLine(String.Format("Error: Empty Section for {0}", nam));
            }
            return section;
        }

        #endregion Saving

        #region Loading

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public bool LoadFromStorage(string fileName)
        {
            using (Storage cd = new Storage(fileName))
            {
                if (cd.Open())
                {
                    Storage2Setup(cd);

                    //Clear Collection and Reset NextIndex back to 0.
                    Clear();
                    fNextIndex = 0;

                    StringCollection streams = null;
                    using (StoragePath sp = new StoragePath("data", cd.Root))
                    {
                        streams = sp.StorageListing(Interop.STGTY.STREAM);
                    } //Using

                    foreach (String streamName in streams)
                    {
                        Storage2Measurement(cd, String.Format("data\\{0}", streamName));
                    }
                }
            } //Using

            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cd"></param>
        /// <param name="index"></param>
        public void Storage2Measurement(Storage cd, Int32 index)
        {
            Storage2Measurement(cd, String.Format("data\\sample{0:000}", index));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cd"></param>
        /// <param name="streamName"></param>
        private void Storage2Measurement(Storage cd, String streamName)
        {
            Debug.WriteLine(String.Format("Reading data\\{0}", streamName));

            IniFile ic = new IniFile();

            //For now Assume One File...
            using (StorageStream aStream = new StorageStream(streamName, cd.Root))
            {
                ic.Text = aStream.Text;
            }

            S si = null;

            Int32 no = ic.ReadInteger("Sample", "No", -1);
            foreach (Serie<T, S>.SerieItem<S> di in this)
            {
                //if (((S)di.Measurement).No == no)
                //{
                //    si = di.Measurement;
                //}
            }

            if (si == null)
            {
                si = AddNew().Measurement;
            }

            foreach (PropertyInfo prop in si.GetType().GetProperties())
            {
                ReadProperty(
                    ic,
                    null,
                    si,
                    prop);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cd"></param>
        private void Storage2Setup(Storage cd)
        {
            IniFile ic = new IniFile();

            using (StorageStream aStream = new StorageStream("serie.ini", cd.Root))
            {
                ic.Text = aStream.Text;
            }

            foreach (PropertyInfo prop in Setup.GetType().GetProperties())
            {
                ReadProperty(
                    ic,
                    null,
                    Setup,
                    prop);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public bool LoadFromFile(string fileName)
        {
            try
            {
                //Clear Collecion and Reset NextIndex back to 0.
                Clear();
                fNextIndex = 0;

                IniFile ic = new IniFile();

                ic.LoadFromFile(fileName);

                foreach (PropertyInfo prop in Setup.GetType().GetProperties())
                {
                    ReadProperty(
                        ic,
                        Setup.GetType().Name,
                        Setup,
                        prop);
                }

                //Trim down number of section if whole colllection is 
                //saved into a single file.
                StringCollection sections = new StringCollection();
                foreach (String section in ic.ReadSections())
                {
                    String sec = section;
                    if (sec.StartsWith(typeof(S).Name))
                    {
                        if (sec.Contains("."))
                        {
                            sec = section.Substring(0, section.IndexOf("."));
                        }
                        if (!sections.Contains(sec))
                        {
                            sections.Add(sec);
                        }
                    }
                }

                //TODO: This generates a new Measurement for each section (if splitted)!
                //      So Trim the section first (strip the .xxx and remove dupes).

                //Debug.Indent();
                foreach (String section in sections)
                {
                    if (section.StartsWith(typeof(S).Name))
                    {
                        //Read Item SectionAttribute
                        S si = AddNew().Measurement;

                        foreach (PropertyInfo prop in si.GetType().GetProperties())
                        {
                            ReadProperty(
                                ic,
                                section,
                                si,
                                prop);
                        }
                    }
                }
                //Debug.Unindent();

                Dirty = false;
            }
            catch
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Just for symmetrie.
        /// </summary>
        /// <param name="b">The Byte to Swap.</param>
        /// <returns>The Swapped Byte.</returns>
        private Byte swap(Byte b)
        {
            return b;
        }

        /// <summary>
        /// Swap Bytes.
        /// </summary>
        /// <param name="w">The Word to Swap.</param>
        /// <returns>The Swapped Word.</returns>
        private UInt16 swap(UInt16 w)
        {
            return ((UInt16)(
                ((0xFF00 & w) >> 8) |
                ((0x00FF & w) << 8)));
        }

        /// <summary>
        /// Swap Bytes.
        /// </summary>
        /// <param name="d">The DWord to Swap.</param>
        /// <returns>The Swapped Dword.</returns>
        private UInt32 swap(UInt32 d)
        {
            return ((UInt32)(
                ((0xFF000000 & d) >> 24) |
                ((0x00FF0000 & d) >> 08) |
                ((0x0000FF00 & d) << 08) |
                ((0x000000FF & d) << 24)));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ic"></param>
        /// <param name="section"></param>
        /// <param name="obj"></param>
        /// <param name="info"></param>
        private void ReadProperty(IniFile ic, String section, Object obj,/* MemberInfo mem*/ PropertyInfo info)
        {
            if (HasSectionAttribute(info))
            {
                string nam = info.Name;                            //Get Name from S
                string typ = info.PropertyType.Name;               //Get Type from S

                //Note: String properties must have an Initial Value or GetValue/SetValue will fail!.
                string defval = "";
                if (info.GetValue(obj, null) != null)
                {
                    defval = info.GetValue(obj, null).ToString();
                }

                String altered = AlteredSection(section, info, nam);
                if (ic.ValueExists(altered, nam))
                {
                    string val = ic.ReadString(altered, nam, defval);

                    if (DoTranslate != null)
                    {
                        val = DoTranslate(altered, nam, val);
                    }

                    //Debug.Indent();
                    //Debug.WriteLine(String.Format("Reading: {0}.{1} = {2}", altered, nam, val));
                    //Debug.Unindent();

                    //Name.Equals("Enum") fails as Name returns the declared type like "SetupOptions"
                    //BaseType.Name should work, but returns ValueType for Int32.
                    try
                    {
                        if (info.PropertyType.IsEnum)
                        {
                            //For Enums                      
                            info.SetValue(obj, Enum.Parse(info.PropertyType, val), null);
                        }
                        else if (typ.Equals("Int32"))
                        {
                            //For Integer Values
                            info.SetValue(obj, Int32.Parse(val), null);
                        }
                        else if (typ.Equals("Double"))
                        {
                            //For Double Values, Replace Decimal Separators by Correct One..
                            val = val.Replace(".", CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator);
                            val = val.Replace(",", CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator);

                            info.SetValue(obj, Double.Parse(val, NumberStyles.AllowExponent | NumberStyles.Number), null);
                        }
                        else if (typ.Equals("String"))
                        {
                            //For String Values
                            info.SetValue(obj, val, null);
                        }
                        else if (typ.Equals("Boolean"))
                        {
                            //For Boolean Values
                            info.SetValue(obj, Boolean.Parse(val), null);
                        }
                        else if (typ.Equals("Byte[]"))
                        {
                            //Val happens to be a hex string...
                            Int32 elementsize = 2 * sizeof(Byte);

                            Byte[] array = new Byte[val.Length / elementsize];

                            for (Int32 i = 0; i < array.Length; i++)
                            {
                                array[i] = Convert.ToByte(val.Substring(i * elementsize, elementsize), 16);
                            }

                            info.SetValue(obj, array, null);
                        }
                        else if (typ.Equals("UInt16[]"))
                        {
                            //Val happens to be a hex string...
                            Int32 elementsize = 2 * sizeof(UInt16);

                            UInt16[] array = new UInt16[val.Length / elementsize];

                            for (Int32 i = 0; i < array.Length; i++)
                            {
                                array[i] = Convert.ToUInt16(val.Substring(i * elementsize, elementsize), 16);
                            }

                            info.SetValue(obj, array, null);
                        }
                        else if (typ.Equals("UInt32[]"))
                        {
                            //Val happens to be a hex string...
                            Int32 elementsize = 2 * sizeof(UInt32);

                            UInt32[] array = new UInt32[val.Length / elementsize];

                            for (Int32 i = 0; i < array.Length; i++)
                            {
                                array[i] = Convert.ToUInt32(val.Substring(i * elementsize, elementsize), 16);
                            }

                            info.SetValue(obj, array, null);
                        }
                        else
                        {
                            Debug.WriteLine(String.Format("Unsupported Property Type: {0} {1}", typ, info.PropertyType.BaseType.Name));
                        }
                    }
                    catch
                    {
                        Debug.WriteLine(String.Format("Error Setting Property: {0} to {1}", nam, val));
                    }
                }
            }
        }

        #endregion Loading

        /// <summary>
        /// Simple Dispose only emitting a warning...
        /// </summary>
        public void Dispose()
        {
            if (Dirty)
            {
                Debug.WriteLine("Warning: Data modified but not saved!");
            }
        }

        #region IDisposable Members

        /// <summary>
        /// IDisposable
        /// </summary>
        void IDisposable.Dispose()
        {
            Dispose();
        }

        #endregion
    }
}
